package classfile

import (
	"fmt"
	"math"
	"unicode/utf16"
)

const (
	CONSTANT_UTF_8                = 1
	CONSTANT_INTEGER              = 3
	CONSTANT_FLOAT                = 4
	CONSTANT_LONG                 = 5
	CONSTANT_DOUBLE               = 6
	CONSTANT_CLASS                = 7
	CONSTANT_STRING               = 8
	CONSTANT_FIELD_REF            = 9
	CONSTANT_METHOD_REF           = 10
	CONSTANT_INTERFACE_METHOD_REF = 11
	CONSTANT_NAME_AND_TYPE        = 12
	CONSTANT_METHOD_HANDLE        = 15
	CONSTANT_METHOD_TYPE          = 16
	CONSTANT_INVOKE_DYNAMIC       = 18
)

type Constant interface {
	ReadConstant(classReader *ClassReader, constant Constant)

	setTag(tag uint8)

	isLongOrDouble() bool
	parseValue(pool ConstantPool, self Constant)
}

type ConstantData struct {
	ClassReader
	capacity int
}

func NewConstantData(bytes []uint8) *ConstantData {
	return &ConstantData{
		ClassReader: ClassReader{data: bytes, pos: 0},
		capacity:    len(bytes),
	}
}

type ConstantStruct struct {
	tag  uint8
	data *ConstantData
}

func (self *ConstantStruct) setTag(tag uint8) {
	self.tag = tag
}

func (self *ConstantStruct) isLongOrDouble() bool {
	return self.tag == CONSTANT_LONG || self.tag == CONSTANT_DOUBLE
}

func (c *ConstantStruct) ReadConstant(classReader *ClassReader, self Constant) {
	switch c.tag {
	case CONSTANT_INTEGER:
		i := self.(*Constant_Integer)
		i.data = NewConstantData(classReader.ReadBytes(4))
		break

	case CONSTANT_FLOAT:
		i := self.(*Constant_Float)
		i.data = NewConstantData(classReader.ReadBytes(4))
		break

	case CONSTANT_LONG:
		i := self.(*Constant_Long)
		i.data = NewConstantData(classReader.ReadBytes(8))
		break

	case CONSTANT_DOUBLE:
		i := self.(*Constant_Double)
		i.data = NewConstantData(classReader.ReadBytes(8))
		break

	case CONSTANT_UTF_8:
		i := self.(*Constant_Utf8)
		i.data = NewConstantData(classReader.ReadBytes(uint32(classReader.ReadUInt16())))
		break
	case CONSTANT_STRING:
		i := self.(*Constant_String)
		i.data = NewConstantData(classReader.ReadBytes(2))
		i.stringIndex = i.data.ReadUInt16()
		break

	case CONSTANT_CLASS:
		i := self.(*Constant_Class)
		i.data = NewConstantData(classReader.ReadBytes(2))
		i.nameIndex = i.data.ReadUInt16()
		break

	case CONSTANT_NAME_AND_TYPE:
		i := self.(*Constant_Name_And_Type)
		i.data = NewConstantData(classReader.ReadBytes(4))
		i.nameIndex = i.data.ReadUInt16()
		i.describeIndex = i.data.ReadUInt16()
		break

	case CONSTANT_FIELD_REF:
		i := self.(*Constant_Field_Ref)
		i.data = NewConstantData(classReader.ReadBytes(4))
		i.classIndex = i.data.ReadUInt16()
		i.nameAndTypeIndex = i.data.ReadUInt16()
		break

	case CONSTANT_METHOD_REF:
		//unsafe.Pointer(&self)
		i := self.(*Constant_Method_Ref)
		i.data = NewConstantData(classReader.ReadBytes(4))
		i.classIndex = i.data.ReadUInt16()
		i.nameAndTypeIndex = i.data.ReadUInt16()
		break

	case CONSTANT_INTERFACE_METHOD_REF:
		i := self.(*Constant_Interface_Method_Ref)
		i.data = NewConstantData(classReader.ReadBytes(4))
		i.classIndex = i.data.ReadUInt16()
		i.nameAndTypeIndex = i.data.ReadUInt16()
		break

	case CONSTANT_METHOD_TYPE:
		i := self.(*Constant_Method_Type)
		i.data = NewConstantData(classReader.ReadBytes(2))
		i.descriptorIndex = i.data.ReadUInt16()
		break

	case CONSTANT_METHOD_HANDLE:
		i := self.(*Constant_Method_Handle)
		i.data = NewConstantData(classReader.ReadBytes(3))
		i.referenceKind = i.data.ReadUInt8()
		i.referenceIndex = i.data.ReadUInt16()
		break

	case CONSTANT_INVOKE_DYNAMIC:
		i := self.(*Constant_InvokeDynamic)
		i.data = NewConstantData(classReader.ReadBytes(4))
		i.bootStrapMethodAttrIndex = i.data.ReadUInt16()
		i.nameAndTypeIndex = i.data.ReadUInt16()
		break
	}

}

func (c ConstantStruct) parseValue(pool ConstantPool, self Constant) {
	switch c.tag {
	case CONSTANT_INTEGER:
		i := self.(*Constant_Integer)
		i.Value = int32(c.data.ReadUInt32())
		break

	case CONSTANT_FLOAT:
		i := self.(*Constant_Float)
		i.Value = math.Float32frombits(c.data.ReadUInt32())
		break

	case CONSTANT_LONG:
		i := self.(*Constant_Long)
		i.Value = int64(c.data.ReadUInt64())
		break

	case CONSTANT_DOUBLE:
		i := self.(*Constant_Double)
		i.Value = math.Float64frombits(c.data.ReadUInt64())
		break

	case CONSTANT_UTF_8:
		i := self.(*Constant_Utf8)
		i.Value = decodeMUTF8(c.data.ReadBytes(uint32(c.data.capacity)))
		//print(i.tag, i.Value, "\n")
		break
	case CONSTANT_STRING:
		i := self.(*Constant_String)
		i.value = pool.GetConstant(i.stringIndex).(*Constant_Utf8)
		break

	case CONSTANT_CLASS:
		i := self.(*Constant_Class)
		i.className = pool.GetConstant(i.nameIndex).(*Constant_Utf8)
		break

	case CONSTANT_NAME_AND_TYPE:
		i := self.(*Constant_Name_And_Type)
		i.name = pool.GetConstant(i.nameIndex).(*Constant_Utf8)
		i.describe = pool.GetConstant(i.describeIndex).(*Constant_Utf8)
		break

	case CONSTANT_FIELD_REF:
		i := self.(*Constant_Field_Ref)
		i.class = pool.GetConstant(i.classIndex).(*Constant_Class)
		i.nameAndType = pool.GetConstant(i.nameAndTypeIndex).(*Constant_Name_And_Type)
		break

	case CONSTANT_METHOD_REF:
		i := self.(*Constant_Method_Ref)
		i.class = pool.GetConstant(i.classIndex).(*Constant_Class)
		i.nameAndType = pool.GetConstant(i.nameAndTypeIndex).(*Constant_Name_And_Type)
		break

	case CONSTANT_INTERFACE_METHOD_REF:
		i := self.(*Constant_Interface_Method_Ref)
		i.class = pool.GetConstant(i.classIndex).(*Constant_Class)
		i.nameAndType = pool.GetConstant(i.nameAndTypeIndex).(*Constant_Name_And_Type)
		break

	case CONSTANT_METHOD_TYPE:
		i := self.(*Constant_Method_Type)
		i.descriptor = pool.GetConstant(i.descriptorIndex).(*Constant_Utf8)
		break

	case CONSTANT_METHOD_HANDLE:

		break

	case CONSTANT_INVOKE_DYNAMIC:
		i := self.(*Constant_InvokeDynamic)
		i.nameAndType = pool.GetConstant(i.nameAndTypeIndex).(*Constant_Name_And_Type)
		break
	}

}

type ConstantPool []Constant

func (self ConstantPool) GetConstant(index uint16) Constant {
	return self[index]
}

// 从常量池中查找类名
func (cp ConstantPool) getClassName(index uint16) string {
	classInfo := cp.GetConstant(index).(*Constant_Class)
	return classInfo.className.Value
}

func ReadConstantPool(cr *ClassReader) ConstantPool {
	size := int(cr.ReadUInt16())
	cs := make([]Constant, size)
	for i := 1; i < size; i++ {
		tag := cr.ReadUInt8()
		var constant = NewConstantInfo(tag)
		constant.setTag(tag)
		cs[i] = constant
		constant.ReadConstant(cr, constant)
		if constant.isLongOrDouble() {
			i += 1
		}
	}
	for i := 1; i < size; i++ {
		c := cs[i]
		c.parseValue(cs, c)
		if c.isLongOrDouble() {
			i += 1
		}
	}
	return cs
}

func NewConstantInfo(tag uint8) Constant {
	switch tag {
	case CONSTANT_INTEGER:
		return &Constant_Integer{}

	case CONSTANT_FLOAT:
		return &Constant_Float{}

	case CONSTANT_LONG:
		return &Constant_Long{}

	case CONSTANT_DOUBLE:
		return &Constant_Double{}

	case CONSTANT_UTF_8:
		return &Constant_Utf8{}

	case CONSTANT_STRING:
		return &Constant_String{}

	case CONSTANT_CLASS:
		return &Constant_Class{}

	case CONSTANT_NAME_AND_TYPE:
		return &Constant_Name_And_Type{}

	case CONSTANT_FIELD_REF:
		return &Constant_Field_Ref{}

	case CONSTANT_METHOD_REF:
		return &Constant_Method_Ref{}

	case CONSTANT_INTERFACE_METHOD_REF:
		return &Constant_Interface_Method_Ref{}

	case CONSTANT_METHOD_TYPE:
		return &Constant_Method_Type{}

	case CONSTANT_METHOD_HANDLE:
		return &Constant_Method_Handle{}

	case CONSTANT_INVOKE_DYNAMIC:
		return &Constant_InvokeDynamic{}
	}
	panic("not supoort tag:" + string(tag))
}

type Constant_T[T interface{}] struct {
	ConstantStruct
	Value T
}

type Constant_Utf8 struct {
	Constant_T[string]
}

type Constant_Integer struct {
	Constant_T[int32]
}

type Constant_Float struct {
	Constant_T[float32]
}

type Constant_Long struct {
	Constant_T[int64]
}

type Constant_Double struct {
	Constant_T[float64]
}

type Constant_String struct {
	ConstantStruct
	stringIndex uint16
	value       *Constant_Utf8
}

func (s Constant_String) StringValue() string {
	return s.value.Value
}

type Constant_Class struct {
	ConstantStruct
	nameIndex uint16
	className *Constant_Utf8
}

func (c Constant_Class) GetClassName() string {
	return c.className.Value
}

type Constant_Name_And_Type struct {
	ConstantStruct
	nameIndex     uint16
	describeIndex uint16
	name          *Constant_Utf8
	describe      *Constant_Utf8
}

func (self Constant_Name_And_Type) GetNameAndType() (string, string) {
	return self.name.Value, self.describe.Value
}

type Constant_Field_Ref struct {
	ConstantStruct
	classIndex       uint16
	nameAndTypeIndex uint16
	class            *Constant_Class
	nameAndType      *Constant_Name_And_Type
}

func (r Constant_Field_Ref) GetClassName() string {
	return r.class.GetClassName()
}

func (r Constant_Field_Ref) GetNameAndType() (string, string) {
	return r.nameAndType.GetNameAndType()
}

type Constant_Method_Ref struct {
	Constant_Field_Ref
}

type Constant_Interface_Method_Ref struct {
	Constant_Field_Ref
}

func (r Constant_Interface_Method_Ref) ClassName() string {
	return r.class.GetClassName()
}

func (r Constant_Interface_Method_Ref) NameAndDescriptor() (string, string) {
	return r.GetNameAndType()
}

func decodeMUTF8(bytes []byte) string {
	utflen := len(bytes)
	chararr := make([]uint16, utflen)

	var c, char2, char3 uint16
	count := 0
	chararr_count := 0

	for count < utflen {
		c = uint16(bytes[count])
		if c > 127 {
			break
		}
		count++
		chararr[chararr_count] = c
		chararr_count++
	}

	for count < utflen {
		c = uint16(bytes[count])
		switch c >> 4 {
		case 0, 1, 2, 3, 4, 5, 6, 7:
			/* 0xxxxxxx*/
			count++
			chararr[chararr_count] = c
			chararr_count++
		case 12, 13:
			/* 110x xxxx   10xx xxxx*/
			count += 2
			if count > utflen {
				panic("malformed input: partial character at end")
			}
			char2 = uint16(bytes[count-1])
			if char2&0xC0 != 0x80 {
				panic(fmt.Errorf("malformed input around byte %v", count))
			}
			chararr[chararr_count] = c&0x1F<<6 | char2&0x3F
			chararr_count++
		case 14:
			/* 1110 xxxx  10xx xxxx  10xx xxxx*/
			count += 3
			if count > utflen {
				panic("malformed input: partial character at end")
			}
			char2 = uint16(bytes[count-2])
			char3 = uint16(bytes[count-1])
			if char2&0xC0 != 0x80 || char3&0xC0 != 0x80 {
				panic(fmt.Errorf("malformed input around byte %v", (count - 1)))
			}
			chararr[chararr_count] = c&0x0F<<12 | char2&0x3F<<6 | char3&0x3F<<0
			chararr_count++
		default:
			/* 10xx xxxx,  1111 xxxx */
			panic(fmt.Errorf("malformed input around byte %v", count))
		}
	}
	// The number of chars produced may be less than utflen
	chararr = chararr[0:chararr_count]
	runes := utf16.Decode(chararr)
	return string(runes)
}
